﻿using System;
using System.Xml;
using System.Text;
using System.Collections.Generic;

using Weazel.Math;
using Weazel.Gezel.Model;

namespace Weazel.Gezel.Exporter
{
  internal class DatapathExporter
  {
    internal static void ToXml(XmlDocument xmlDoc, XmlElement parent, Datapath datapath) 
    {
      XmlElement datapathElement = xmlDoc.CreateElement("Datapath");
      datapathElement.SetAttribute("name", datapath.LocalName);
      datapathElement.SetAttribute("id", datapath.Name);

      // check if datapath is just a clone
      if (datapath.ClonedFromDatapath != null)
      {
        datapathElement.SetAttribute("instanceOf", datapath.ClonedFromDatapath);
        return; // no need to specify anything else for this datapath
      }

      if (datapath.InPorts.Count > 0)
      {
        XmlComment inportsComments = xmlDoc.CreateComment("inports");
        datapathElement.AppendChild(inportsComments);

        foreach (InPort inport in datapath.InPorts.Values)
        {
          PortExporter.ToXml(xmlDoc, datapathElement, inport);
        }
      }

      if (datapath.OutPorts.Count > 0)
      {
        XmlComment outportsComment = xmlDoc.CreateComment("outports");
        datapathElement.AppendChild(outportsComment);
        
        foreach (OutPort outport in datapath.OutPorts.Values)
        {
          PortExporter.ToXml(xmlDoc, datapathElement, outport);
        }
      }

      if (datapath.Registers.Count > 0)
      {
        XmlComment registersComment = xmlDoc.CreateComment("register");
        datapathElement.AppendChild(registersComment);

        foreach (Register register in datapath.Registers.Values)
        {
          RegisterExporter.ToXml(xmlDoc, datapathElement, register);
        }
      }

      if (datapath.Signals.Count > 0)
      {
        XmlComment signalsComment = xmlDoc.CreateComment("signals");
        datapathElement.AppendChild(signalsComment);

        foreach (Signal signal in datapath.Signals.Values)
        {
          SignalExporter.ToXml(xmlDoc, datapathElement, signal);
        }
      }

      if(datapath.LookupTables.Count > 0) {

        XmlComment lutComment = xmlDoc.CreateComment("lookup tables");
        datapathElement.AppendChild(lutComment);

        foreach(LookupTable lut in datapath.LookupTables.Values) 
        {
          lutToXml(xmlDoc, datapathElement, lut);
        }
      }

      if (datapath.Tracers.Count > 0)
      {
        XmlComment tracersComment = xmlDoc.CreateComment("tracers");
        datapathElement.AppendChild(tracersComment);

        foreach (SignalTrace trace in datapath.Tracers)
        {
          XmlElement tracerElement = xmlDoc.CreateElement("Tracer");
          tracerElement.SetAttribute("expression", trace.Expression.ToString());
          tracerElement.SetAttribute("filename", trace.Filename);

          datapathElement.AppendChild(tracerElement);
        }
      }     

      XmlComment sfgsComment = xmlDoc.CreateComment("signal flow graphs");
      datapathElement.AppendChild(sfgsComment);

			foreach (SignalFlowGraph sfg in datapath.SignalFlowGraphs.Values)
			{
				SignalFlowGraphExporter.ToXml(xmlDoc, datapathElement, sfg);
			}

      parent.AppendChild(datapathElement);
    }

    /// <summary>
    /// Converts a lookup table to xml.
    /// </summary>
    /// <param name="xmlDoc"></param>
    /// <param name="datapathElement"></param>
    /// <param name="lut"></param>
    private static void lutToXml(XmlDocument xmlDoc, XmlElement datapathElement, LookupTable lut)
    {
      XmlElement lutElement = xmlDoc.CreateElement("LookupTable");
      lutElement.SetAttribute("name", lut.LocalName);
      lutElement.SetAttribute("id", lut.Name);
      lutElement.SetAttribute("type", lut.Signed ? "signed" : "unsigned");
      lutElement.SetAttribute("width", lut.Width.ToString());

      foreach (Integer value in lut.Values)
      {
        XmlElement valueElement = xmlDoc.CreateElement("Value");
        valueElement.InnerText = value.ToString();

        lutElement.AppendChild(valueElement);
      }

      datapathElement.AppendChild(lutElement);
    }

    /// <summary>
    /// Converts a gezel datapath object to its
    /// string representation.
    /// </summary>
    /// <param name="textWriter">recieves output</param>
    /// <param name="datapath">datapath instance to convert</param>
    internal static void ToGezel(System.CodeDom.Compiler.IndentedTextWriter textWriter, Datapath datapath)
    {
      // check if datapath is a clone
      if (datapath.ClonedFromDatapath != null)
      {
        textWriter.WriteLine("dp {0} : {1}", datapath.LocalName, datapath.ClonedFromDatapath);
        return;
      }


      string portsString;
      if (datapath.InPorts.Count > 0 || datapath.OutPorts.Count > 0)
      {
        // sort ports to ensure that they are output 
        // in the their original order since
        // gezel (unfortunately) uses positional 
        // port binding instead of named binding
        List<Port> ports = new List<Port>(datapath.InPorts.Count + datapath.OutPorts.Count);
        foreach (Port port in datapath.InPorts.Values)
          ports.Add(port);
        foreach (Port port in datapath.OutPorts.Values)
          ports.Add(port);
        ports.Sort();

        StringBuilder buffer = new StringBuilder("(\n");
        foreach (Port port in ports)
        {
          buffer.AppendFormat(
            "    {0} {1} : {2}({3});\n", 
            port is InPort ? "in" : "out",  
            port.LocalName, 
            port.Signed ? "tc" : "ns", 
            port.Width
          );
        }

        // remove last semi-colon
        buffer.Remove(buffer.Length - 2, 1);
        buffer.Append(")");
        portsString = buffer.ToString();
      }
      else
      {
        portsString = string.Empty;
      }


      textWriter.WriteLine("dp {0} {1}", datapath.LocalName, portsString);
      textWriter.WriteLine("{");
      textWriter.Indent++;

      foreach (Register register in datapath.Registers.Values)
      {
        RegisterExporter.ToGezel(textWriter, register);
      }

			if (datapath.Registers.Count > 0)
				textWriter.WriteLine();

      foreach (Signal signal in datapath.Signals.Values)
      {
        SignalExporter.ToGezel(textWriter, signal);
      }

			if (datapath.Signals.Count > 0)
				textWriter.WriteLine();

      foreach (LookupTable lut in datapath.LookupTables.Values)
      {
        textWriter.WriteLine("lookup {0} : {1}({2}) = ", lut.LocalName, lut.Signed ? "tc" : "ns", lut.Width);
        textWriter.WriteLine("{");
        textWriter.Indent++;

        for (int i = 0; i < lut.Values.Length - 1; i++)
        {
          textWriter.WriteLine("{0}, ", lut.Values[i].ToString());
        }
        textWriter.WriteLine(lut.Values[lut.Values.Length - 1].ToString());

        textWriter.Indent--;
        textWriter.WriteLine("};");
      }

      foreach (SignalTrace tracer in datapath.Tracers)
      {
        textWriter.WriteLine("$trace({0}, \"{1}\");", tracer.Expression.ToString(), tracer.Filename);
      }
			
      foreach (string childDp in datapath.ChildDatapathRefs.Keys)
      {
        string[] nets = datapath.ChildDatapathRefs[childDp];

				if (nets.Length > 0)
				{
					textWriter.WriteLine("use {0} (", childDp);
					textWriter.Indent++;

					// for all but the last one ..
					for (int i = 0; i < nets.Length - 1; i++)
						textWriter.WriteLine("{0},", nets[i]);

					textWriter.WriteLine("{0}", nets[nets.Length - 1]);

					textWriter.Indent--;
					textWriter.WriteLine(");");
				}
				else
				{
					textWriter.WriteLine("use {0};", childDp);
				}
      }

      foreach (SignalFlowGraph sfg in datapath.SignalFlowGraphs.Values)
      {
        SignalFlowGraphExporter.ToGezel(textWriter, sfg);
      }      

      textWriter.Indent--;
      textWriter.WriteLine("}");
      textWriter.WriteLine();
    }
  }
}
